/**
 * @file simpleKoa application对象
 */

let EventEmitter = require('events');
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

class Application extends EventEmitter {
    /**
     * 构造函数
     */
    constructor() {
        super();
        this.middlewares = [];
        this.context = context;
        this.request = request;
        this.response = response;
    }
    /**
     * 开启http server并传入callback
     */
    listen(...args) {
        let server = http.createServer(this.callback());
        server.listen(...args);
    }
    /**
     * 中间件挂载
     * @param {Function} middleware 中间件函数
     */
    use(middleware) {
        this.middlewares.push(middleware);
    }
    composeb(){
        var middleware=this.middlewares;
        return function (context, next) {
            // last called middleware #
            let index = -1
            return dispatch(0)
            function dispatch (i) {
              //if (i <= index) return Promise.reject(new Error('next() called multiple times'))
              index = i
              let fn = middleware[i];
              console.log(i,middleware.length,!!fn)
              if (i === middleware.length) fn = next
              if (!fn) return Promise.resolve()
              try {
                return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
              } catch (err) {
                return Promise.reject(err)
              }
            }
          }
    }
    /**
     * 中间件合并方法，将中间件数组合并为一个中间件
     * @return {Function}
     */
    compose() {
        // 将middlewares合并为一个函数，该函数接收一个ctx对象
        //next 默认是一个resolve 从最后开始循环，把next传递进入use函数，当做next
        //上一个use 再接收返回的async next，一直到第一个。
        //这样最后Nex就是第一个use，执行到await next时，next就是下一个use，这样一直到，
        //默认的resolve ，它执行完后会执行最后一个use里await后的代码，从内向外
        return async ctx => {
            function createNext(middleware, oldNext) {
                return async () => {
                    await middleware(ctx, oldNext);
                }
            }
            let len = this.middlewares.length;
            console.log(len)
            let next = async () => {
                return Promise.resolve();
            };
            for (let i = len - 1; i >= 0; i--) {
                let currentMiddleware = this.middlewares[i]
                //console.log(currentMiddleware.toString())
                next = createNext(currentMiddleware, next);
                //console.log(next.toString())
            }
            await next();
        };
    }
    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let onerror = (err) => this.onerror(err, ctx);
            let fn = this.composeb  ();
            return fn(ctx).then(respond).catch(onerror);
        };
    }
    /**
     * 构造ctx
     * @param {Object} req node req实例
     * @param {Object} res node res实例
     * @return {Object} ctx实例
     */
    createContext(req, res) {
        // 针对每个请求，都要创建ctx对象
        let ctx = Object.create(this.context);
        ctx.request = Object.create(this.request);
        ctx.response = Object.create(this.response);
        ctx.req = ctx.request.req = req;
        ctx.res = ctx.response.res = res;
        return ctx;
    }
    /**
     * 对客户端消息进行回复
     * @param {Object} ctx ctx实例
     */
    responseBody(ctx) {
        console.log('responseBody')
        let content = ctx.body;
        if (typeof content === 'string') {
            ctx.res.end(content);
        }
        else if (typeof content === 'object') {
            ctx.res.end(JSON.stringify(content));
        }
    }
    /**
     * 错误处理
     * @param {Object} err Error对象
     * @param {Object} ctx ctx实例
     */
    onerror(err, ctx) {
        if (err.code === 'ENOENT') {
            ctx.status = 404;
        }
        else {
            ctx.status = 500;
        }
        let msg = err.message || 'Internal error';
        ctx.res.end(msg);
        this.emit('error', err);
    }
}
module.exports = Application;